package org.syntaxlisp.core.match.tree;

import org.syntaxlisp.core.match.MatchUnit;
import org.syntaxlisp.core.match.MatchUnitType;
import org.syntaxlisp.core.utils.Pair;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;

/**
 * 为了解决
 * @author HuangYijun
 * @data 2023-08-04 14:22
 **/
public class StringTreeMap<V> implements Map<String,V> {

    private Map<String, V> store;

    CharacterNode entry;

    public StringTreeMap() {
        store = new HashMap<>();
        entry = new CharacterNode();
    }

    public static void main(String[] args) {
        StringTreeMap<Integer> tmap = new StringTreeMap<>();
        tmap.put("hello", 1);
        tmap.put("hell", 2);
        tmap.put("he", 3);
        System.out.println(tmap.remove("hell"));
        System.out.println(tmap.remove("hello"));
        System.out.println(tmap.get("he"));
    }

    /**
     * 匹配 string map 存储 key
     * @param inputSeq 输入序列
     * @param inputOffset next offset
     * @return
     */
    public List<Pair<String, Integer>> matchKey(List<MatchUnit> inputSeq, int inputOffset) {

        List<Pair<String, Integer>> matchResList = new ArrayList<>();

        CharacterNode curNode = entry;
        StringBuilder matchKey = new StringBuilder();
        while (inputOffset < inputSeq.size() && !CollectionUtils.sizeIsEmpty(curNode.map)) {
            MatchUnit curInput = inputSeq.get(inputOffset);
            if (MatchUnitType.STRING == curInput.getType()) {
                int i = 0;
                String inputString = (String) curInput.getVal();
                while (i < inputString.length()) {
                    Optional<CharacterNode> curCharNode = curNode.get(inputString.charAt(i));
                    if (curCharNode.isPresent()) {
                        curNode = curCharNode.get();
                        matchKey.append(inputString.charAt(i));
                        if (curNode.endOfWord
                                && (i == inputString.length() - 1)) {
                            matchResList.add(Pair.<String, Integer>builder()
                                                       .first(matchKey.toString())
                                                       .second(inputOffset + 1)
                                                       .build());
                        }
                        i += 1;
                    } else {
                        break;
                    }
                }
                if (i < inputString.length()) {
                    break;
                }
            } else {
                break;
            }
            inputOffset += 1;
        }
        return matchResList;
    }

    public List<Pair<V, Integer>> match(List<MatchUnit> inputSeq, int inputOffset) {
        List<Pair<V, Integer>> matchResList = new ArrayList<>();
        matchKey(inputSeq, inputOffset)
                .forEach(res -> {
                    V matchRes = get(res.getFirst());
                    matchResList.add(Pair.<V, Integer>builder()
                                             .first(matchRes)
                                             .second(res.getSecond())
                                             .build());
                });
        return matchResList;
    }

    private static class CharacterNode {

        final Map<Character, CharacterNode> map;

        final char value;

        boolean endOfWord;

        public CharacterNode () {
            map = new HashMap<>();
            value = '0';
            endOfWord = false;
        }

        public CharacterNode (char value) {
            map = new HashMap<>();
            this.value = value;
        }

        public CharacterNode put (String val, int offset) {
            CharacterNode cur = map.computeIfAbsent(val.charAt(offset), CharacterNode::new);
            cur.endOfWord = val.length() - 1 == offset;
            return cur;
        }

        private static final int NO_MATCH = 0;

        private static final int REMOVE_PREFIX = 1;

        private static final int REMOVE_WORD = 2;

        public Optional<CharacterNode> get(char c) {
            return Optional.ofNullable(map.get(c));
        }

        public int remove (String val, int offset) {
            if (offset >= val.length()) {
                if (!map.isEmpty()) {
                    if (endOfWord) {
                        endOfWord = false;
                        return REMOVE_PREFIX;
                    } else {
                        return NO_MATCH;
                    }
                } else {
                    return REMOVE_WORD;
                }
            }
            char c = val.charAt(offset);
            if (map.containsKey(c)) {
                CharacterNode removeNode = map.get(c);
                int removeTag = removeNode.remove(val, offset + 1);
                if (REMOVE_WORD == removeTag) {
                    map.remove(c);
                    return map.isEmpty() && !endOfWord
                           ? REMOVE_WORD
                           : REMOVE_PREFIX;
                }
                return removeTag;
            } else {
                return NO_MATCH;
            }
        }
    }

    @Override
    public int size() {
        return store.size();
    }

    @Override
    public boolean isEmpty() {
        return store.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return store.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return store.containsValue(value);
    }

    @Override
    public V get(Object key) {
        return store.get(key);
    }

    @Override
    public V put(String key, V value) {
        CharacterNode curNode = entry;
        for (int i = 0; i < key.length(); i ++) {
            curNode = curNode.put(key, i);
        }
        return store.put(key, value);
    }

    @Override
    public V remove(Object key) {
        entry.remove((String) key, 0);
        return store.remove(key);
    }

    @Override
    public void putAll(Map<? extends String, ? extends V> m) {
        m.forEach(this::put);
    }

    @Override
    public void clear() {
        entry.map.clear();
        store.clear();
    }

    @Override
    public Set<String> keySet() {
        return store.keySet();
    }

    @Override
    public Collection<V> values() {
        return store.values();
    }

    @Override
    public Set<Entry<String, V>> entrySet() {
        return store.entrySet();
    }
}
